package fr.inserm.um915.bdibd.model;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import com.sleepycat.bind.EntryBinding;
import com.sleepycat.je.DatabaseEntry;


public class LinkageRow
	{
	static private final int BYTES_PER_IBDSTATUS=6; //(3 proba * 2 bytes)
	static private final int USHRT_MAX=65535;
	private byte array[];
	
	public static class Binding implements EntryBinding<LinkageRow>
		{
		@Override
		public LinkageRow entryToObject(DatabaseEntry entry)
			{
			return new LinkageRow(entry.getData());
			}
		
		@Override
		public void objectToEntry(LinkageRow o, DatabaseEntry e)
			{
			e.setData(o.array);
			}
		}
	
	public static final Binding BINDING=new Binding();
	
	
	public LinkageRow()
		{
		this.array=new byte[0];
		}
	
	public LinkageRow(int n_pairs)
		{
		this.array=new byte[n_pairs*BYTES_PER_IBDSTATUS];
		IBDStatus status=new IBDStatus();
		for(int i=0;i< n_pairs;++i)
			{
			set(i, status);
			}
		}
	
	private LinkageRow(byte array[])
		{
		if(array.length%BYTES_PER_IBDSTATUS!=0) throw new IllegalArgumentException();
		this.array=new byte[array.length];
		System.arraycopy(array, 0, this.array, 0, array.length);
		}
	
	public float getIbd(int pairid,WhichIBD ibdidx)
		{
		return getIbd(pairid,ibdidx.ordinal());
		}
	
	public float getIbd(int pairid,int ibdidx)
		{
		if(pairid>=size()) return -1.0f;
		return get(pairid,ibdidx);
		}
	
	
	public float getIbd0(int pairid)
		{
		return getIbd(pairid,0);
		}
	
	public float getIbd1(int pairid)
		{
		return getIbd(pairid,1);
		}
	
	public float getIbd2(int pairid)
		{
		return getIbd(pairid,2);
		}
	
	public List<IBDStatus> toArray()
		{
		List<IBDStatus> L=new ArrayList<IBDStatus>(size());
		for(int i=0;i< size();++i) L.add(get(i));
		return L;
		}
	
	
	public IBDStatus get(int pair_id)
		{
		if(pair_id>=size()) return new IBDStatus();
		return new IBDStatus(
				get(pair_id, 0),
				get(pair_id, 1),
				get(pair_id, 2)
				);
		}
	
	public void set(int pair_id,IBDStatus status)
		{
		
		if(this.array.length<=pair_id*BYTES_PER_IBDSTATUS)
			{
			byte array2[]=new byte[(pair_id+1)*BYTES_PER_IBDSTATUS];
			System.arraycopy(array, 0, array2, 0, array.length);
			int oldsize=size();
			this.array=array2;
			IBDStatus nil=new IBDStatus();
			for(int i=oldsize;i< pair_id;++i)
				{
				set(i,nil);
				}
			}
		set(pair_id,0,status.ibd0);
		set(pair_id,1,status.ibd1);
		set(pair_id,2,status.ibd2);
		}
	
	public int size()
		{
		return array.length/BYTES_PER_IBDSTATUS;
		}
	
	private void set(int pair_id,int probaidx,float proba)
		{
		if(proba>1.0f) throw new IllegalArgumentException("p="+proba);
		int v;
		if(proba<0)
			{
			v=USHRT_MAX;
			}
		else
			{
			v=(int)(proba*(USHRT_MAX-1));
			if(v>(USHRT_MAX-1)) throw new IllegalArgumentException(String.valueOf(proba));
			}

		array[pair_id*BYTES_PER_IBDSTATUS+probaidx*2+0]=(byte) (((v >>>  8) & 0xff)) ;
		array[pair_id*BYTES_PER_IBDSTATUS+probaidx*2+1]=(byte) (((v >>>  0) & 0xff));
		}
	
	private float get(int pair_id,int probaidx)
		{
		int v=	((array[pair_id*BYTES_PER_IBDSTATUS+probaidx*2+0] & 0xff) <<  8) +
		  		((array[pair_id*BYTES_PER_IBDSTATUS+probaidx*2+1] & 0xff) <<  0)
		  		;
		
		if(v==USHRT_MAX) return -1;
    	return v/((float)(USHRT_MAX-1));
		}
	
	boolean isEmpty()
		{
		return size()==0;
		}
	
	
	@Override
	public int hashCode()
		{
		return  Arrays.hashCode(array);
		}


	@Override
	public boolean equals(Object obj)
		{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		LinkageRow other = (LinkageRow) obj;
		if (!Arrays.equals(array, other.array))
			return false;
		return true;
		}


	@Override
	public String toString()
		{
		StringBuilder b=new StringBuilder();
		for(int i=0;i< size();++i)
			{
			b.append(String.valueOf(i)).append(" : ").append(get(i)).append("\n");
			}
		return b.toString();
		}
	
	public interface Stats
		{
		public int getCount();
		public float getMean();
		}
	
	
	private static class StatsImpl implements Stats
		{
		private double total=0f;
		private int count=0;
		public int getCount()
			{
			return this.count;
			}
		public float getMean()
			{
			return (float)(total/count);
			}
		}
	
	
	public Stats getStatistics(float ibdLimit,List<Integer> pairIndexes,WhichIBD ibd_index_012)
		{
		StatsImpl stat=new StatsImpl();
		
		for(int index:pairIndexes)
			{
			float i0=this.getIbd(index,ibd_index_012);
			if(ibd_index_012==WhichIBD.USE_IBD_0)
				{
				if(i0!=-1f && i0 < ibdLimit)
					{
					stat.total+=i0;
					stat.count++;
					}
				}
			else
				{
				if(i0!=-1f && i0 > ibdLimit)
					{
					stat.total+=i0;
					stat.count++;
					}
				}
			}
		return stat;
		}
	
	
	
	public static void main(String[] args) throws Exception
		{
		LinkageRow r=new LinkageRow();
		java.util.Random rand=new java.util.Random();
		IBDStatus ibd=null;
		IBDStatus ibd2;
		for(int j=0;j<= USHRT_MAX;++j)
			{
			int pair_id=rand.nextInt(50);
			float f=(j/((float)(USHRT_MAX-1)));
			if(j==USHRT_MAX) f=-1.0f;
			
			ibd=new IBDStatus();
			r.set(pair_id, ibd);
			ibd2=r.get(pair_id);
			
			if(Math.abs(ibd2.getIbd0()-ibd.getIbd0())>0.01)
				{
				throw new RuntimeException("#"+ibd+" "+ibd2);
				} 
			
			
			
			ibd=new IBDStatus(f,f,f);
			r.set(pair_id, ibd);
			ibd2=r.get(pair_id);
			if(Math.abs(ibd2.getIbd0()-ibd.getIbd0())>0.01)
				{
				throw new RuntimeException(ibd+" "+ibd2);
				}
			
			
			
			}

		}
	}
